package xj.graph2d;

import java.awt.AWTError;
import java.awt.Adjustable;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JScrollBar;
import javax.swing.UIManager;

import xj.util.gui.Point2DFloat;

public class ScrollCanvas extends Container {

  public static final int SCROLLBARS_AS_NEEDED = 0;

  public static final int SCROLLBARS_ALWAYS = 1;

  public static final int SCROLLBARS_NEVER = 2;

  public static final int RULER_HEIGHT = 20;

  protected boolean showMargin = false;

  protected boolean showRuler = false;

  private Ruler hruler;

  private Ruler vruler;

  private int scrollbarDisplayPolicy;

  private JScrollBar vAdjustable;

  private JScrollBar hAdjustable;

  private DrawCanvas canvas;

  public ScrollCanvas(DrawCanvas canvas) {
    this(SCROLLBARS_AS_NEEDED, canvas);
  }

  public ScrollCanvas(int scrollbarDisplayPolicy, DrawCanvas canvas) {
    super.setLayout(null);
    switch (scrollbarDisplayPolicy) {
    case SCROLLBARS_NEVER:
    case SCROLLBARS_AS_NEEDED:
    case SCROLLBARS_ALWAYS:
      this.scrollbarDisplayPolicy = scrollbarDisplayPolicy;
      break;
    default:
      throw new IllegalArgumentException("illegal scrollbar display policy");
    }

    ScrollCanvasListener scrollhandler = new ScrollCanvasListener();
    vAdjustable = new JScrollBar(Adjustable.VERTICAL);
    hAdjustable = new JScrollBar(Adjustable.HORIZONTAL);
    vAdjustable.addAdjustmentListener(scrollhandler);
    hAdjustable.addAdjustmentListener(scrollhandler);

    hruler = new Ruler(Ruler.HORIZONTAL, this);
    vruler = new Ruler(Ruler.VERTICAL, this);

    if (canvas != null) {
      this.canvas = canvas;
      canvas.scrollcanvas = this;
      add(canvas);
    }
    add(vAdjustable);
    add(hAdjustable);
    add(hruler);
    add(vruler);
  }

  public DrawCanvas getCanvas() {
    return canvas;
  }

  public int getScrollbarDisplayPolicy() {
    return scrollbarDisplayPolicy;
  }

  // device space
  public Dimension getCanvasSize() {
    return canvas.getSize();
  }

  public int getHScrollbarHeight() {
    return hAdjustable.getSize().height;
  }

  public int getVScrollbarWidth() {
    return vAdjustable.getSize().width;
  }

  public Adjustable getVAdjustable() {
    return vAdjustable;
  }

  public Adjustable getHAdjustable() {
    return hAdjustable;
  }

  public boolean isShowMargin() {
    return showMargin;
  }

  public void setShowMargin(boolean showMargin) {
    this.showMargin = showMargin;
  }

  public boolean isShowRuler() {
    return showRuler;
  }

  public void setShowRuler(boolean showRuler) {
    if (this.showRuler != showRuler) {
      this.showRuler = showRuler;
      doLayout();
    }
  }

  public void setScrollPosition(float x, float y, boolean notify) {
    setScrollPosition(x, y);
    if (notify) {
      canvas.setViewportLocation(x, y, false);
    }
  }

  public void setScrollPosition(float x, float y) {
    if (debug) {
      System.out.println("ScrollCanvas.setScrollPosition() enter: x=" + x + " y=" + y);
    }
    Point2D vloc = new Point2D.Float(x, y);
    Point loc = canvas.virtualToPhysical(vloc);
    listenerActive = false;
    curX += loc.x;
    curY += loc.y;
    hAdjustable.setValue(curX);
    vAdjustable.setValue(curY);
    hruler.setStart(x);
    vruler.setStart(y);
    listenerActive = true;

    if (debug) {
      System.out.println("ScrollCanvas.setScrollPosition() leave: x=" + x  + " y=" + y);
    }
  }

  public void setScrollPosition(Point2D p) {
    if (p != null) {
      setScrollPosition((float) p.getX(), (float) p.getY());
    }
  }

  public Point2D getScrollPosition() {
    return new Point2DFloat(hAdjustable.getValue(), vAdjustable.getValue());
  }

  @Override
  public final void setLayout(LayoutManager mgr) {
    throw new AWTError("ScrollCanvas controls layout");
  }

  protected void adjust() {
    doLayout();
  }

  @Override
  public void doLayout() {
    if (debug) {
      System.out.println("ScrollCanvas.doLayout()");
    }

    // viewport physical size
    Dimension viewportDim = getSize();
    if (viewportDim.width <= 0 || viewportDim.height <= 0) {
      return;
    }

    // virtual canvas rect
    Rectangle2D virtualCanvasRect2 = canvas.getVirtualCanvasRectangle();
    Rectangle virtualCanvasRect = canvas.virtualToPhysical(virtualCanvasRect2);
    Insets insets = getInsets();

    boolean vAdjustableNeeded;
    boolean hAdjustableNeeded;

    switch (scrollbarDisplayPolicy) {
    case SCROLLBARS_NEVER:
      vAdjustableNeeded = false;
      hAdjustableNeeded = false;
      break;

    case SCROLLBARS_AS_NEEDED:
      vAdjustableNeeded = ((viewportDim.height - insets.top - insets.bottom) < virtualCanvasRect.height);
      hAdjustableNeeded = ((viewportDim.width - insets.left - insets.right) < virtualCanvasRect.width);
      break;

    case SCROLLBARS_ALWAYS:
      vAdjustableNeeded = true;
      hAdjustableNeeded = true;
      break;

    default:
      throw new IllegalArgumentException("illegal scrollbar display policy");
    }

    int vadjWidth = vAdjustableNeeded ? SCROLLBAR_WIDTH : 0;
    int hadjHeight = hAdjustableNeeded ? SCROLLBAR_WIDTH : 0;

    int vRulerWidth = showRuler ? RULER_HEIGHT : 0;
    int hRulerHeight = showRuler ? RULER_HEIGHT : 0;

    // set canvas position
    // viewport position and size
    int xPos = insets.left + vRulerWidth;
    int yPos = insets.top + hRulerHeight;
    int canvasWidth = (viewportDim.width - insets.left - insets.right
	- vadjWidth - vRulerWidth);
    int canvasHeight = (viewportDim.height - insets.top - insets.bottom
	- hadjHeight - hRulerHeight);
    canvas.setBounds(xPos, yPos, canvasWidth, canvasHeight);

    // set scroll bar position
    hAdjustable.setVisible(hAdjustableNeeded);
    vAdjustable.setVisible(vAdjustableNeeded);
    if (hAdjustableNeeded) {
      hAdjustable.setBounds(xPos, yPos + canvasHeight, canvasWidth, hadjHeight);
    }
    if (vAdjustableNeeded) {
      vAdjustable.setBounds(xPos + canvasWidth, yPos, vadjWidth, canvasHeight);
    }

    // set ruler position
    if (showRuler) {
      hruler.setBounds(xPos, yPos - hRulerHeight, canvasWidth, hRulerHeight);
      vruler.setBounds(xPos - vRulerWidth, yPos, vRulerWidth, canvasHeight);
    }

    // set viewport origin
    // virtual canvas size, device space
    int virtualCanvasWidth = Math.max(virtualCanvasRect.width, canvasWidth);
    int virtualCanvasHeight = Math.max(virtualCanvasRect.height, canvasHeight);

    Rectangle2D viewportRect = canvas.getViewportRectangle();
    float originX = (float) viewportRect.getX();
    float originY = (float) viewportRect.getY();
    Point2D viewportLoc = canvas.getViewportLocation();
    Point viewportPhysicalLoc = canvas.virtualToPhysical(viewportLoc);
    curX = viewportPhysicalLoc.x - virtualCanvasRect.x;
    curY = viewportPhysicalLoc.y - virtualCanvasRect.y;

    // flush to the right if there is a gap
    if (originX + viewportRect.getWidth() > virtualCanvasRect2.getX()
	+ virtualCanvasRect2.getWidth()) {
      originX = (float) (virtualCanvasRect2.getX()
	  + virtualCanvasRect2.getWidth() - viewportRect.getWidth());
      curX = virtualCanvasWidth - canvasWidth;
    }
    // flush to the bottom if there is a gap
    if (originY + viewportRect.getHeight() > virtualCanvasRect2.getY()
	+ virtualCanvasRect2.getHeight()) {
      originY = (float) (virtualCanvasRect2.getY()
	  + virtualCanvasRect2.getHeight() - viewportRect.getHeight());
      curY = virtualCanvasHeight - canvasHeight;
    }

    // flush to the left if there is a gap
    if (originX < virtualCanvasRect2.getX()) {
      originX = (float) virtualCanvasRect2.getX();
      curX = 0;
      hruler.setStart(0);
    }
    // flush to the top if there is a gap
    if (originY < virtualCanvasRect2.getY()) {
      originY = (float) virtualCanvasRect2.getY();
      curY = 0;
      vruler.setStart(0);
    }

    // center the canvas when the canvas is smaller than the view port
    int hMargin = (showMargin ? SCROLL_CANVAS_MARGIN : 0);
    if (viewportRect.getWidth() > virtualCanvasRect2.getWidth()) {
      originX -= (viewportRect.getWidth() - virtualCanvasRect2.getWidth()) / 2;
      // originX = (float) (virtualCanvasRect2.getX() -
      // (viewportRect.getWidth() - virtualCanvasRect2.getWidth()) / 2);
      hMargin = 0;
      hruler.setStart(originX);
    }

    int vMargin = (showMargin ? SCROLL_CANVAS_MARGIN : 0);
    if (viewportRect.getHeight() > virtualCanvasRect2.getHeight()) {
      originY -= (viewportRect.getHeight() - virtualCanvasRect2.getHeight()) / 2;
      // originY = (float) (virtualCanvasRect2.getY() -
      // (viewportRect.getHeight() - virtualCanvasRect2.getHeight()) / 2);
      vMargin = 0;
      vruler.setStart(originY);
    }

    canvas.setViewportLocation(originX, originY, false);

    listenerActive = false;

    // hAdjustable.setMinimum(0);
    // vAdjustable.setMinimum(0);
    // hAdjustable.setMaximum(virtualCanvasWidth);
    // vAdjustable.setMaximum(virtualCanvasHeight);

    hAdjustable.setMinimum(0 - hMargin);
    vAdjustable.setMinimum(0 - vMargin);
    hAdjustable.setMaximum(virtualCanvasWidth + hMargin);
    vAdjustable.setMaximum(virtualCanvasHeight + vMargin);

    vAdjustable.setVisibleAmount(canvasHeight);
    hAdjustable.setVisibleAmount(canvasWidth);
    vAdjustable.setUnitIncrement(10);
    hAdjustable.setUnitIncrement(10);
    vAdjustable.setBlockIncrement(canvasHeight / 2);
    hAdjustable.setBlockIncrement(canvasWidth / 2);
    listenerActive = true;

    repaint();
    canvas.repaint();
    if (debug) {
      System.out.println("ScrollCanvas.doLayout() done");
      System.out.println("  virtualCanvasRect2=" + virtualCanvasRect2);
      System.out.println("  virtualCanvasRect=" + virtualCanvasRect);
      System.out.println("  viewportDim=" + viewportDim);
      System.out.println("  canvasWidth=" + canvasWidth);
      System.out.println("  canvasHeight=" + canvasHeight);
      System.out.println("  virtualCanvasWidth=" + virtualCanvasWidth);
      System.out.println("  virtualCanvasHeight=" + virtualCanvasHeight);
      System.out.println("  originX=" + originX);
      System.out.println("  originY=" + originY);
      System.out.println("  curX=" + curX);
      System.out.println("  curY=" + curY);
    }

  }

  private static int SCROLLBAR_WIDTH = 12;

  private static int SCROLL_CANVAS_MARGIN = 20;

  static {
    Object o = UIManager.get("ScrollBar.width");
    if (o != null && o instanceof Integer) {
      Integer i = (Integer) o;
      SCROLLBAR_WIDTH = i.intValue();
    }
  }

  class ScrollCanvasListener implements AdjustmentListener {

    public void adjustmentValueChanged(AdjustmentEvent e) {

      if (!listenerActive) {
	return;
      }

      Adjustable adj = e.getAdjustable();
      int value = e.getValue();
      Point2D vloc; // = canvas.getViewportLocation();
      loc.x = hAdjustable.getValue() - curX;
      loc.y = vAdjustable.getValue() - curY;
      curX = hAdjustable.getValue();
      curY = vAdjustable.getValue();
      vloc = canvas.physicalToVirtual(loc);
      float xStart = (float) vloc.getX();
      float yStart = (float) vloc.getY();
      canvas.setViewportLocation(xStart, yStart, false);
      hruler.setStart(xStart);
      vruler.setStart(yStart);

      if (debug) {
	System.out.println("ScrollCanvasListener: curX=" + curX + "  curY="
	    + curY + " loc=" + loc + " vloc=" + vloc);
      }
    }

    Point loc = new Point(); // canvas.virtualToPhysical(vloc);
  }

  protected boolean listenerActive = true;

  protected int curX, curY;

  protected static final boolean debug = false;

}
